package com.exam.api.common.web;

import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.exam.api.common.exception.GlobalException;
import com.exam.api.config.validation.DefinitionValidationException;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.util.Optional;
import java.util.Set;
import java.util.StringJoiner;

/**
 * @Author LemonUnique
 * @Create 2020-11-25 17:58
 * @Description 通用ResultData类
 */
@Schema(description = "通用ResultData类")
@Setter
@Getter
public class ResultData<T> implements Serializable {
    /**
     * 消息状态码
     */
    @Schema(description = "消息状态码")
    @Accessors(chain = true)
    private Integer code;

    /**
     * 消息
     */
    @Schema(description = "消息")
    @Accessors(chain = true)
    private String message;

    /**
     * 返回数据
     */
    @Schema(description = "返回数据")
    @Accessors(chain = true)
    private T data;

    /**
     * 请求Id
     */
    @Schema(description = "请求Id")
    private String requestId;

    /**
     * 请求时间
     */
    @Schema(description = "请求时间")
    private LocalDateTime requestTime;

    public ResultData() {
        this.requestId = IdWorker.getIdStr();
        this.requestTime = LocalDateTime.now();
    }

    private ResultData(ResultCode resultCode) {
        this();
        this.code = resultCode.getCode();
        this.message = resultCode.getMessage();
    }

    private ResultData(ResultCode resultCode, T data) {
        this();
        this.code = resultCode.getCode();
        this.message = resultCode.getMessage();
        this.data = data;
    }

    private ResultData(Integer code, String message, T data) {
        this();
        this.code = code;
        this.message = message;
        this.data = data;
    }

    /**
     * 响应成功(带返回数据)
     *
     * @param data 返回数据
     * @return Result
     */
    public static <T> ResultData<T> success(T data) {
        return new ResultData<>(ResultCode.SUCCESS, data);
    }

    /**
     * 响应成功
     *
     * @return ResultData<T>
     */
    public static <T> ResultData<T> success() {
        return new ResultData<>(ResultCode.SUCCESS);
    }

    /**
     * 成功带消息
     *
     * @param msg 消息
     * @param <T>
     * @return ResultData<T>
     */
    public static <T> ResultData<T> success(String msg) {
        return new ResultData<>(ResultCode.SUCCESS.getCode(), msg, null);
    }

    /**
     * 响应错误
     *
     * @param <T>
     * @return ResultData<T>
     */
    public static <T> ResultData<T> error() {
        return new ResultData<>(ResultCode.FAILURE, null);
    }

    /**
     * 响应错误
     *
     * @param msg 消息
     * @param <T>
     * @return ResultData<T>
     */
    public static <T> ResultData<T> error(String msg) {
        return new ResultData<>(ResultCode.FAILURE.getCode(), msg, null);
    }

    /**
     * @param code 状态码
     * @param msg  消息
     * @param <T>
     * @return ResultData<T>
     */
    public static <T> ResultData<T> error(Integer code, String msg) {
        return new ResultData<>(code, msg, null);
    }

    public static ResultData<Object> error(BindingResult result) {
        Object target = result.getTarget();
        FieldError fieldError = result.getFieldError();
        try {
            ConstraintViolation<?> constraintViolation =
                    (ConstraintViolation<?>) ReflectUtil.getFieldValue(fieldError, "violation");
            if (ObjUtil.isNotNull(constraintViolation)) {
                return error(Set.of(constraintViolation));
            }
            if (ObjUtil.isNull(fieldError)) {
                return ResultData.bind(ResultCode.PARAMETER_ERROR);
            }
            if (ObjUtil.isNull(target)) {
                String message = new StringJoiner(StrPool.COLON + StrPool.C_SPACE)
                        .add(fieldError.getField())
                        .add(formattingMessage(fieldError.getDefaultMessage()))
                        .toString();
                return error(message);
            }
            return new ResultData<>()
                    .setCode(ResultCode.FAILURE.getCode())
                    .setMessage(formatMessage(target.getClass(), fieldError));
        } catch (Exception e) {
            return ResultData.bind(ResultCode.PARAMETER_ERROR);
        }
    }

    private static String formatMessage(Class<?> targetClass, FieldError error) {
        Object fieldName = Optional.ofNullable(
                getFieldStr(targetClass, error.getField())
        ).orElse(error.getField() + StrPool.COLON + StrPool.C_SPACE);
        return fieldName + formattingMessage(error.getDefaultMessage());
    }

    public static ResultData<Object> error(DefinitionValidationException exception) {
        Set<ConstraintViolation<?>> constraintViolations = exception.getConstraintViolations();
        return error(constraintViolations);
    }

    public static ResultData<Object> error(ConstraintViolationException exception) {
        Set<ConstraintViolation<?>> constraintViolations = exception.getConstraintViolations();
        return error(constraintViolations);
    }

    public static ResultData<Object> error(Set<ConstraintViolation<?>> constraintViolations) {
        try {
            Optional<ConstraintViolation<?>> constraintViolationOpt = constraintViolations
                    .stream()
                    .findFirst();
            if (constraintViolationOpt.isEmpty()) {
                return ResultData.bind(ResultCode.PARAMETER_ERROR);
            }
            ConstraintViolation<?> constraintViolation = constraintViolationOpt.get();
            if (!constraintViolation.getMessageTemplate().contains("{jakarta.validation.")) {
                return ResultData.error(constraintViolation.getMessage());
            }
            var property = ReUtil.delAll("^[a-zA-Z0-9]*\\.", constraintViolation.getPropertyPath().toString());
            String message = new StringJoiner(StrPool.COLON + StrPool.C_SPACE)
                    .add(property)
                    .add(constraintViolation.getMessage())
                    .toString();
            return new ResultData<>()
                    .setCode(ResultCode.FAILURE.getCode())
                    .setMessage(message);
        } catch (Exception e) {
            return ResultData.bind(ResultCode.PARAMETER_ERROR);
        }
    }


    private static Object getFieldStr(Class<?> targetClass, String fieldStr) {
        if (StrUtil.isBlank(fieldStr)) {
            return null;
        }
        Field field = ReflectUtil.getField(targetClass, fieldStr);
        if (field == null) {
            return null;
        }
        Object value = AnnotationUtil.getAnnotationValue(field, Schema.class);
        if (value != null) {
            if (StrUtil.isBlank(value.toString())) {
                value = null;
            }
        }
        return value;
    }

    private static String formattingMessage(String message) {
        if (message == null) {
            return null;
        }
        return message.replaceAll(StrUtil.NULL, "空");
    }


    /**
     * 根据 MySQL影响行数
     *
     * @param rows
     * @param success 成功参数
     * @param error   失败参数
     * @param <T>
     * @return ResultData<T>
     */
    public static <T> ResultData<T> sqlResult(Integer rows, ResultCode success, ResultCode error) {
        if (rows > 0) {
            return ResultData.bind(success);
        } else {
            return ResultData.bind(error);
        }
    }

    /**
     * 根据 MySQL影响行数
     *
     * @param rows 影响行数
     * @param <T>
     * @return ResultData.success(); || ResultData.error();
     */
    public static <T> ResultData<T> sqlResult(Integer rows) {
        return sqlResult(rows, ResultCode.SUCCESS, ResultCode.FAILURE);
    }

    /**
     * 根据 result
     *
     * @param result
     * @param <T>
     * @return ResultData.success(); || ResultData.error();
     */
    public static <T> ResultData<T> sqlResult(boolean result) {
        if (result) {
            return success();
        } else {
            return error();
        }
    }

    /**
     * 构建返回消息
     *
     * @param resultCode 返回Code
     * @param <T>
     * @return ResultData<T> 返回消息
     */
    public static <T> ResultData<T> bind(ResultCode resultCode) {
        return new ResultData<>(resultCode, null);
    }

    /**
     * 构建异常返回消息
     *
     * @param e   全局异常
     * @param <T>
     * @return 返回消息
     */
    public static <T> ResultData<T> exception(GlobalException e) {
        return error(e.getCode(), e.getMessage());
    }


    @JsonIgnore
    public String toJsonStr() {
        return JSONUtil.toJsonStr(this);
    }

    @Override
    @JsonIgnore
    public String toString() {
        return "ResultData{" + "code=" + code + ", message='" + message + '\'' + ", data=" + data + '}';
    }
}
